package com.nq.common.interceptor;

import com.nq.annotation.RepeatSubmit;
import com.nq.config.RepeatableReadRequestWrapper;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {

    public static final String REPEAT_PARAMS = "repeat_params";
    public static final String REPEAT_TIME = "repeat_time";
    public static final String REPEAT_SUBMIT_KEY = "repeat_submit_key";
    public static final String HEADER = "Authorization";

    @Resource
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断是否是映射controller层的方法直接通过
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //获取Method对象
            Method method = handlerMethod.getMethod();
            //获取@RepeatSubmit 注解
            RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
            //判断是否为空，如果为空则直接放行，如果不为空则进行重复提交校验
            if (repeatSubmit != null) {
                //校验是否重复提交，如果没有则放行，如果是重复提交则返回状态码：500 message："重复提交时的文本描述"
                if (isRepeatSubmit(request, repeatSubmit)) {
                    Map<String, Object> map = new HashMap<>();
                    //设置状态码
                    map.put("status", 500);
                    //设置描述信息
                    map.put("message", repeatSubmit.message());
                    //设置响应的contentType和编码格式
                    response.setContentType("application/json;charset=utf-8");
                    //new ObjectMapper().writeValueAsString(map)将map对象转换成json字符串
                    response.getWriter().write(new ObjectMapper().writeValueAsString(map));
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 判断是否重复提交，返回 true 表示是重复提交
     *
     * @param request
     * @param repeatSubmit
     * @return
     */
    private boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit repeatSubmit) {
        //请求参数字符串
        String nowParams = "";
        if (request instanceof RepeatableReadRequestWrapper) {
            try {
                //获取请求体
                nowParams = ((RepeatableReadRequestWrapper) request).getReader().readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //否则说明请求参数是 key-value 格式的
        if (StringUtils.isEmpty(nowParams)) {
            try {
                //获取所有请求参数并转换为json字符串
                nowParams = new ObjectMapper().writeValueAsString(request.getParameterMap());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Map<String, Object> nowDataMap = new HashMap<>();
        //key: repeat_params value: nowParams
        nowDataMap.put(REPEAT_PARAMS, nowParams);
        //key: repeat_time value: 时间戳
        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
        // /工程路径/controller层路径
        String requestURI = request.getRequestURI();
        //request.getHeader(“Authorization”) Bearer xxxx
        String header = request.getHeader(HEADER);
        //cacheKey = repeat_submit_key + /工程路径/controller层路径 / xxxx
        String cacheKey = REPEAT_SUBMIT_KEY + requestURI + header.replace("Bearer ", "");
        //从缓存中获取cacheKey的值
        Object cacheObject = redisTemplate.opsForValue().get(cacheKey);
        if (cacheObject != null) {
            Map<String, Object> map = (Map<String, Object>) cacheObject;
            //校验参数、间隔时间是否满足重复提交的校验条件，均满足则返回true，否则返回false
            if (compareParams(map, nowDataMap) && compareTime(map, nowDataMap, repeatSubmit.interval())) {
                return true;
            }
        }
        //从缓存中没有获取到cacheKey的值，设置到redis中
        /**
         * @param1: cacheKey = repeat_submit_key + /工程路径/controller层路径 / xxxx
         * @parma2: 请求数据Map对象:包含请求参数、请求时间戳
         * @parma3: 两次请求的间隔时间，也是redis key的过期时间
         * @parma4: 过期时间的单位MILLISECONDS 毫秒
         */
        redisTemplate.opsForValue().set(cacheKey, nowDataMap.toString(), repeatSubmit.interval(), TimeUnit.MILLISECONDS);
        return false;
    }

    /**
     * 对应本次请求和上一次请求的相隔时间是否超过我们设置的间隔时间interval
     * 如果不超过则满足重复提交的校验条件返回true
     * 如果超过则不满足，返回false
     * @param map
     * @param nowDataMap
     * @param interval
     * @return
     */
    private boolean compareTime(Map<String, Object> map, Map<String, Object> nowDataMap, int interval) {
        // 上一次请求的时间
        Long time1 = (Long) map.get(REPEAT_TIME);
        // 本次请求的时间
        Long time2 = (Long) nowDataMap.get(REPEAT_TIME);
        //对比时间差，如果小于我们设置的两个请求之间的间隔时间，则说明满足重复提交的条件返回true
        if ((time2 - time1) < interval) {
            return true;
        }
        //不满足重复提交的间隔时间则返回false
        return false;
    }

    /**
     * 比较本次请求和上一次请求的数据是否一样
     * @param map
     * @param nowDataMap
     * @return
     */
    private boolean compareParams(Map<String, Object> map, Map<String, Object> nowDataMap) {
        String nowParams = (String) nowDataMap.get(REPEAT_PARAMS);
        String dataParams = (String) map.get(REPEAT_PARAMS);
        return nowParams.equals(dataParams);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
